/***************************************************************************
Name: JSView
Description: Helps you to view the source code of external js and css files
Author: Ron Beckman
Homepage: http://forum.softwareblaze.com

Copyright (C) 2008  Ron Beckman

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to:

Free Software Foundation, Inc.
51 Franklin Street
Fifth Floor
Boston, MA  02110-1301
USA
***************************************************************************/


window.addEventListener("load", jsviewPageInfoLoad, false);

function jsviewPageInfoLoad() {
	jsviewPageInfo.init();
}

var jsviewPageInfo = {

	_styleElements: [],
	_scriptElements: [],

	getWin: function(name) {
		return Components.classes["@mozilla.org/appshell/window-mediator;1"].
		getService(Components.interfaces.nsIWindowMediator).
		getMostRecentWindow(name);
	},

	getAppInfo: function() {
		return Components.classes['@mozilla.org/xre/app-info;1'].
		createInstance(Components.interfaces.nsIXULAppInfo);
	},

	getPrefs: function() {
		return Components.classes["@mozilla.org/preferences-service;1"].
		getService(Components.interfaces.nsIPrefService).
		getBranch("jsview.");
	},

	getCacheService: function() {
		return Components.classes["@mozilla.org/network/cache-service;1"].
		getService(Components.interfaces.nsICacheService).
		createSession("HTTP", 0, true);
	},

	convertFileToUrl: function(file) {
		return Components.classes["@mozilla.org/network/protocol;1?name=file"].
		createInstance(Components.interfaces.nsIFileProtocolHandler).
		getURLSpecFromFile(file);
	},

	getStr: function(name) {
		return document.getElementById("jsview-bundle").getString(name);
	},

	addCommas: function(numOld) {
		var numNew = parseInt(numOld);
		numNew = numNew.toString();
		var rgx = /(\d+)(\d{3})/;
		while (rgx.test(numNew)) {
			numNew = numNew.replace(rgx, '$1' + ',' + '$2');
		}
		return numNew;
	},

	getCellData: function(tree,row,col) {
		if (row == null) row = tree.currentIndex;
		if (col == null) col = 0;

		try {
			var cell = tree.view.getCellText(row,tree.columns.getColumnAt(col));
		} catch(e) { }

		return cell;
	},

	init: function() {
		//populate the scripts tree
		this.loadScripts();

		//populate the sheets tree
		this.loadSheets();

		//populate the frames tree
		this.loadFrames();
	},

	loadFrames: function() {
		var url,size,cacheKey,entry;
		var prefs = this.getPrefs();
		var cache = this.getCacheService();
		var filesize = prefs.getBoolPref('fileSizeInKB');

		//get string translations
		var kb = this.getStr('jsview.KB');
		var bytes = this.getStr('jsview.Bytes');

		//remove all tree items
		var child = document.getElementById('jsview-frChild');
		while (child.firstChild) {
			child.removeChild(child.firstChild);
		}

		var frames = this.getFrames();
		//we start at 1 since 0 is window.content
		for (var i=1; i<frames.length; ++i) {
			url = frames[i].location.href;
			try {
				//get filesize for url if cached
				cacheKey = url.replace(/#.*$/,'');
				entry = cache.openCacheEntry(cacheKey, Components.interfaces.nsICache.ACCESS_READ, false);
				if (filesize) {
					size = (entry.dataSize / 1024).toFixed(2) + ' ' + kb;
				} else {
					size = this.addCommas(entry.dataSize) + ' ' + bytes;
				}
			} catch(e) {
				size = '??'; //display something to indicate size unknown
			}
			child.appendChild(this.createItem([url,size]));
		}
	},

	loadScripts: function() {
		var url,size,cacheKey,entry,item,index;
		var prefs = this.getPrefs();
		var cache = this.getCacheService();
		var filesize = prefs.getBoolPref('fileSizeInKB');

		//get string translations
		var kb = this.getStr('jsview.KB');
		var bytes = this.getStr('jsview.Bytes');
		var unk = this.getStr('jsview.Unknown');
		var embd = this.getStr('jsview.Embedded');

		//remove all tree items
		var child = document.getElementById('jsview-jsChild');
		while (child.firstChild) {
			child.removeChild(child.firstChild);
		}

		var scripts = this.getScripts();
		for (var i=0; i<scripts.length; ++i) {
			if (scripts[i] instanceof Components.interfaces.nsIDOMHTMLScriptElement) {
				url = embd;
				this._scriptElements[i] = scripts[i];
				if (filesize) {
					size = (scripts[i].innerHTML.length / 1024).toFixed(2) + ' ' + kb;
				} else {
					size = this.addCommas(scripts[i].innerHTML.length) + ' ' + bytes;
				}
			} else {
				try {
					url = scripts[i];
					cacheKey = url.replace(/#.*$/,'');
					//get filesize for url if cached
					entry = cache.openCacheEntry(cacheKey, Components.interfaces.nsICache.ACCESS_READ, false);
					if (filesize) {
						size = (entry.dataSize / 1024).toFixed(2) + ' ' + kb;
					} else {
						size = this.addCommas(entry.dataSize) + ' ' + bytes;
					}
				} catch(e) {
					size = unk;
				}
			}

			item = this.createItem([url,size]);
			if (url == embd) item.setAttribute('jsview',i);
			child.appendChild(item);
		}
	},

	loadSheets: function() {
		var url,size,cacheKey,entry,item,index;
		var prefs = this.getPrefs();
		var cache = this.getCacheService();
		var filesize = prefs.getBoolPref('fileSizeInKB');

		//get string translations
		var kb = this.getStr('jsview.KB');
		var bytes = this.getStr('jsview.Bytes');
		var unk = this.getStr('jsview.Unknown');
		var embd = this.getStr('jsview.Embedded');

		//remove all tree items
		var child = document.getElementById('jsview-cssChild');
		while (child.firstChild) {
			child.removeChild(child.firstChild);
		}

		var sheets = this.getSheets();
		for (var i=0; i<sheets.length; ++i) {
			if (sheets[i] instanceof Components.interfaces.nsIDOMHTMLStyleElement) {
				url = embd;
				this._styleElements[i] = sheets[i];
				if (filesize) {
					size = (sheets[i].innerHTML.length / 1024).toFixed(2) + ' ' + kb;
				} else {
					size = this.addCommas(sheets[i].innerHTML.length) + ' ' + bytes;
				}
			} else {
				try {
					url = sheets[i];
					cacheKey = url.replace(/#.*$/,'');
					//get filesize for url if cached
					entry = cache.openCacheEntry(cacheKey, Components.interfaces.nsICache.ACCESS_READ, false);
					if (filesize) {
						size = (entry.dataSize / 1024).toFixed(2) + ' ' + kb;
					} else {
						size = this.addCommas(entry.dataSize) + ' ' + bytes;
					}
				} catch(e) {
					size = unk;
				}
			}

			item = this.createItem([url,size]);
			if (url == embd) item.setAttribute('jsview',i);
			child.appendChild(item);
		}
	},

	createItem: function(cells) {
		var cell;
		var item = document.createElement('treeitem');
		var row = document.createElement('treerow');

		for (var i=0; i<cells.length; ++i) {
			cell = document.createElement('treecell');
			cell.setAttribute('label',cells[i]);
			row.appendChild(cell);
		}

		item.appendChild(row);
		return item;
	},

	copyLink: function() {
		var tree = this.getTree();
		if (!tree.view || !tree.view.selection || !tree.columns || !tree.contentView) return false;

		var urls = this.getSelectedUrls(tree);
		var brows = this.getAppInfo();
		var win = this.getWin((brows.name!='Songbird') ? 'navigator:browser' : 'Songbird:Main');
		win.jsview.copyLink(urls.join('\r\n'));
		return false;
	},

	getTree: function() {
		var id;
		//var panel = document.getElementById('tabbox').selectedPanel;
                var tabElm = document.getElementById('tabbox') || //for Fx 2
                             document.getElementById('mainDeck'); //for Fx 3
                var panel = tabElm.selectedPanel;
		if (panel.id == 'jsview-jsPanel') {
			id = 'jsview-jsTree';
		} else if (panel.id == 'jsview-cssPanel') {
			id = 'jsview-cssTree';
		} else {
			id = 'jsview-frTree';
		}
		return document.getElementById(id);
	},

	getSelectedUrls: function(tree) {
		var cell;
		var urls = [];
		var start = {};
		var end = {};
		var embd = this.getStr('jsview.Embedded');
		var range = tree.view.selection.getRangeCount();

		for (var i=0; i<range; ++i) {
			tree.view.selection.getRangeAt(i,start,end);
			for (var j=start.value; j<=end.value; ++j) {
				cell = this.getCellData(tree,j,0)
				if (cell == embd) {
					cell = this.getTempFile(tree,j);
				}
				urls.push(cell);
			}
		}
		return urls;
	},

	getTempFile: function(tree,row) {
		var url;
		var ext = (tree.id == 'jsview-jsTree') ? '.js' : '.css';
		var obj = (tree.id == 'jsview-jsTree') ? '_scriptElements' : '_styleElements';

		var item = tree.contentView.getItemAtIndex(row);
		var index = parseInt(item.getAttribute('jsview'));
		if (this[obj][index].hasAttribute('jsview')) {
			url = this[obj][index].getAttribute('jsview');
		} else {
			var temp = this.getTempDirectory();
			var dt = new Date();
			var time = dt.getTime();
			var rdm = time.toString().substr(-4);
			var name = 'jsview.' + rdm + ext;
			temp.append(name);
			try {
				this.writeFile(temp, this[obj][index].innerHTML, 0x04 | 0x08 | 0x20);
				url = this.convertFileToUrl(temp);
			} catch(e) {
				alert(this.getStr('jsview.WriteError'));
				url = 'about:blank';
			}
			this[obj][index].setAttribute('jsview',url);
		}
		return url;
	},

	viewSource: function(type) {
		var tree = this.getTree();
		if (!tree.view || !tree.view.selection || !tree.columns || !tree.contentView) return false;

		var urls = this.getSelectedUrls(tree);
		for (var i=0; i<urls.length; ++i) {
			setTimeout('jsviewPageInfo.viewSource2("'+urls[i]+'","'+type+'")',i*500);
		}
		return false;
	},

	viewSource2: function(url,type) {
		var brows = this.getAppInfo();
		var win = this.getWin((brows.name!='Songbird') ? 'navigator:browser' : 'Songbird:Main');
		win.jsview.viewSource(url,type);
	},

	getFrames: function() {
		var brows = this.getAppInfo();
		var win = this.getWin((brows.name!='Songbird') ? 'navigator:browser' : 'Songbird:Main');
		var frms = [win.content];

		for (var i=0; i<frms.length; ++i) {
			for (var j=0; j<frms[i].frames.length; ++j) {
				frms.push(frms[i].frames[j]);
			}
			//we break after reaching 50 to avoid a possible endless loop from
			//occurring if the website has coded frames within frames.........
			if (i >= 50) break;
		}
		return frms;
	},

	getScripts: function() {
		var scriptUrls = [];
		var scripts,source,srcAttr;

		//get a reference to all dom windows in current page
		var wins = this.getFrames();

		for (var i=0; i<wins.length; ++i) {
			scripts = wins[i].document.getElementsByTagName('script');
			for (var j=0; j<scripts.length; ++j) {
				if (scripts[j].hasAttribute('src')) {
					//use getAttribute for xul script elements
					//use .src for html script elements
					srcAttr = scripts[j].getAttribute('src');
					source = (srcAttr.substr(0,9) == 'chrome://') ? srcAttr : scripts[j].src;
					//we need to make sure all anchors are removed from the urls
					scriptUrls.push(source.replace(/#.*$/,''));
				} else {
					scriptUrls.push(scripts[j]);
				}
			}
		}

		return scriptUrls;
	},

	getSheets: function() {
		var sheets,loc;
		var styleUrls = [];

		//get a reference to all dom windows in current page
		var wins = this.getFrames();

		for (var i=0; i<wins.length; ++i) {
			sheets = wins[i].document.styleSheets;
			for (var j=0; j<sheets.length; ++j) {
				if (sheets[j].ownerNode &&
				    sheets[j].ownerNode instanceof Components.interfaces.nsIDOMHTMLStyleElement) {
					styleUrls.push(sheets[j].ownerNode);
				} else if (sheets[j].href) {
					//we need to make sure all anchors are removed from the urls
					styleUrls.push(sheets[j].href.replace(/#.*$/,''));
				}
				styleUrls = styleUrls.concat(this.getImportedCSS(sheets[j]));
			}
		}

		//filter the styleUrls array for any urls that match window locations
		for (var i=0; i<wins.length; ++i) {
			loc = wins[i].location.href.replace(/#.*$/,'');
			styleUrls = styleUrls.filter(function(value) { return value != loc; });
		}

		return styleUrls;
	},

	getImportedCSS: function(css) {
		var urls = [];
		var sheets = [css];

		for (var i=0; i<sheets.length; ++i) {
			for (var j=0; j<sheets[i].cssRules.length; ++j) {
				if (sheets[i].cssRules[j].type != 3) break;

				if (sheets[i].cssRules[j].styleSheet &&
				    sheets[i].cssRules[j].styleSheet.href) {
					//we need to make sure all anchors are removed from the urls
					urls.push(sheets[i].cssRules[j].styleSheet.href.replace(/#.*$/,''));
					sheets.push(sheets[i].cssRules[j].styleSheet);
				}
			}
			//we break after reaching 50 to avoid a possible endless loop from
			//occurring if a stylesheet tries to import itself over and over...
			if (i >= 50) break;
		}
		return urls;
	},

	getTempDirectory: function() {
		return Components.classes["@mozilla.org/file/directory_service;1"].
		getService(Components.interfaces.nsIProperties).
		get("TmpD", Components.interfaces.nsIFile);
	},

	getFileOutputStream: function() {
		return Components.classes["@mozilla.org/network/file-output-stream;1"].
		createInstance(Components.interfaces.nsIFileOutputStream);
	},

	writeFile: function(file, fileContent, flags) {
		var strm = this.getFileOutputStream();
		strm.QueryInterface(Components.interfaces.nsIOutputStream);
		strm.QueryInterface(Components.interfaces.nsISeekableStream);
		strm.init(file, flags, 420, 0);
		strm.write(fileContent, fileContent.length);
		strm.flush();
		strm.close();
	}
};
